/* Copyright 2008 Bas van den Berg
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "SessionManager.h"
#include "EventServer.h"
#include "LoginSession.h"
#include "PlayerCreationSession.h"
#include "PlayerSession.h"
#include "StringBuilder.h"
#include "Error.h"
#include "myassert.h"

#include "Logger.h"
#include "Database.h"

#define FOREACH_SESSION for (PlayerSessionsConstIter iter=players.begin(); iter!=players.end(); ++iter)

using namespace Chronos;
using namespace std;

SessionManager::SessionManager(int port, EventServer& eventServer_, Parser* parser_, Database& database_)
    : eventServer(eventServer_)
    , socketServer(port)
    , parser(parser_)
    , database(database_)
{}


SessionManager::~SessionManager() {
    for (LoginSessionsIter iter=logins.begin(); iter!=logins.end(); ++iter) {
        delete *iter;
    }
    FOREACH_SESSION {
        eventServer.generateEvent(Event::PLAYER_LOGOUT, *(*iter));
        delete *iter;
    }
}


void SessionManager::handlePlayerSessions() {
    PlayerSessionsIter playersIter = players.begin();
    while (playersIter != players.end()) {
        PlayerSession* session = *playersIter;
        switch (session->handlePulse()) {
		case PlayerSession::OK:
            ++playersIter;
			break;
		case PlayerSession::FINISHED:
            string name = session->getName();
            eventServer.generateEvent(Event::PLAYER_LOGOUT, *session);
            delete session;
            playersIter = players.erase(playersIter);
            LOG(INFO, "SessionManager: player %s has logged out", name.c_str());
			break;
		}
    }
}


void SessionManager::handleLoginSessions() {
    LoginSessionsIter loginsIter = logins.begin();
    while (loginsIter != logins.end()) {
		LoginSession* session = *loginsIter;
		switch (session->handlePulse()) {
		case LoginSession::INPROGRESS:
			++loginsIter;
            continue;
		case LoginSession::FAIL:
			break;
		case LoginSession::SUCCESS:
			addPlayer(session->getName(), session->getConnection());
			break;
        case LoginSession::NEW_PLAYER:
            creationSessions.push_back(new PlayerCreationSession(session->getConnection(), database));
            break;
		}
		delete session;
		loginsIter = logins.erase(loginsIter);
    }
}


void SessionManager::handlePlayerCreationSessions() {
    CreationSessionsIter sessionIter = creationSessions.begin();
    while (sessionIter != creationSessions.end()) {
		PlayerCreationSession* session = *sessionIter;
		switch (session->handlePulse()) {
		case PlayerCreationSession::INPROGRESS:
			++sessionIter;
            continue;
		case PlayerCreationSession::FAIL:
			break;
		case PlayerCreationSession::SUCCESS:
			addPlayer(session->getName(), session->getConnection());
			break;
        }
		delete session;
		sessionIter = creationSessions.erase(sessionIter);
    }
}


void SessionManager::checkNewConnections() {
    try {
        if (socketServer.hasNewConnection()) {    // only accept 1 connection per tick
            Connection* connection = socketServer.acceptConnection();
            LoginSession* newSession = new LoginSession(connection, database);
            logins.push_back(newSession);
        }
    } catch (ConnectionError& e) {
        LOG(WARN, "SessionManager: %s() ConnectionError(%s)", __func__, e.what());
    }
}


void SessionManager::handlePulse() {

    handlePlayerSessions();
    
    handleLoginSessions();

    handlePlayerCreationSessions();

    checkNewConnections();
   
    FOREACH_SESSION (*iter)->flush();
}


void SessionManager::addPlayer(const string& name, Connection* connection) {
    PlayerSession* session = findPlayerSession(name);
    if (session) {
        session->changeConnection(connection); 
        LOG(INFO, "SessionManager: player %s reconnected", name.c_str());
    } else {
        LOG(INFO, "SessionManager: player %s logged in", name.c_str());
        PlayerSession* newPlayer = new PlayerSession(name, connection, *parser, database);
        players.push_back(newPlayer);
        eventServer.generateEvent(Event::PLAYER_LOGIN, *newPlayer);
    }
}


PlayerSession* SessionManager::findPlayerSession(const string& name) const {
    FOREACH_SESSION {
        if ((*iter)->getName() == name) {
            return (*iter);
        }
    }
    return 0;
}


void SessionManager::visitSessions(SessionVisitor& visitor) {
    FOREACH_SESSION {
        visitor.handle(*(*iter));
    }
}


void SessionManager::printStatus(StringBuilder& buffer) const {
    buffer << "  SessionManager: " << players.size() << " player(s)\n";
}

